﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

using Microsoft.Scripting;
using Microsoft.Scripting.Ast;
using Microsoft.Linq.Expressions;

namespace DlrBasic.Parser.Ast
{
    class VariableOrCall : DlrBasicExpression
    {
        string name;

        public string Name
        {
            get { return name; }
        }

        DlrBasicExpression[] arguments;

        public int ArgumentsCount
        {
            get { return arguments == null ? 0 : arguments.Length; }
        }

        public VariableOrCall(SourceSpan span, string name, DlrBasicExpression[] arguments)
            : base(span)
        {
            this.name = name;
            this.arguments = arguments;
        }

        public bool IsFunction()
        {
            return Name.StartsWith("FN", StringComparison.InvariantCultureIgnoreCase) ||
                Runtime.DlrBasicFunctions.IsFunctionPresent(Name);
        }

        public Type GetVariableType(DlrBasicGenerator tg)
        {
            switch (name[name.Length - 1])
            {
                case '%':
                    return TypeConversionUtils.IntType;
                default: // TODO query context
                case '!':
                    return TypeConversionUtils.SingleType;
                case '#':
                    return TypeConversionUtils.DoubleType;
                case '$':
                    return TypeConversionUtils.StringType;                
            }
        }

        public string GetFullVariableName()
        {
            if (arguments == null || arguments.Length == 0)
                return name.ToUpperInvariant();
            else
                return name.ToUpperInvariant() +"`" + arguments.Length.ToString();
        }

        protected internal override Expression Generate(DlrBasicGenerator generator)
        {
            if (IsFunction())
                return GenerateFunctionCall(generator);
            else if (ArgumentsCount == 0 && generator.IsFunctionParameter(Name))
                return GenerateFunctionParameters(generator);


            Type itemType = GetVariableType(generator);
            System.Reflection.MethodInfo method = TypeConversionUtils.FindExactMethod(
                typeof(Runtime.DlrBasicMemory), "Get", itemType, true);
            
            Expression index = Expression.Null(typeof(int[]));
            if (ArgumentsCount > 0)
            {
                Expression[] bounds = new Expression[arguments.Length];
                for (int i = 0; i < bounds.Length; i++)
			    {
			        bounds[i] = Expression.ConvertHelper(
                        TypeConversionUtils.BuildConversionTo(typeof(short),
                        arguments[i].Generate(generator)), typeof(int));
			    }
                index = Expression.NewArrayInit(typeof(int), bounds);
            }

            return Expression.Call(new StateMemberGenerator(generator.CurrentStateParameter).GetMemoryVariable(),
                method,
                Expression.Constant(SymbolTable.StringToCaseInsensitiveId(Name)),
                index);
        }

        private Expression GenerateFunctionParameters(DlrBasicGenerator generator)
        {
            return generator.GetFunctionParameter(Name);
        }

        private Expression GenerateFunctionCall(DlrBasicGenerator generator)
        {
            if (Name.StartsWith("FN", StringComparison.InvariantCultureIgnoreCase))
            {
                Expression[] callArguments = new Expression[ArgumentsCount];
                for (int i = 0; i < callArguments.Length; i++)
                {
                    callArguments[i] = Expression.ConvertHelper(
                        arguments[i].Generate(generator), typeof(object));
                }
                Expression argumentsArray = Expression.NewArrayHelper(
                    typeof(object), callArguments);

                return Expression.Call(new StateMemberGenerator(generator.CurrentStateParameter).GetUserFunctionsVariable(),
                    typeof(Runtime.DlrBasicUserFunctions).GetMethod("Invoke"),
                    Expression.Constant(Name), argumentsArray);
            }
            else
            {
                Expression[] callArguments = new Expression[ArgumentsCount];
                for (int i = 0; i < callArguments.Length; i++)
                {
                    callArguments[i] = arguments[i].Generate(generator);
                }

                Type[] argumentsTypes = new Type[ArgumentsCount];
                for (int i = 0; i < argumentsTypes.Length; i++)
			    {
                    argumentsTypes[i] = callArguments[i].Type;    			 
			    }

                MethodInfo method = Runtime.DlrBasicFunctions.GetBindableMethod(
                    Name, argumentsTypes);
                if(method != null)
                {
                    ParameterInfo[] parameters = method.GetParameters();
                    for (int i = 0; i < parameters.Length; i++)
                    {
                        callArguments[i] = TypeConversionUtils.BuildConversionTo(
                            parameters[i].ParameterType, callArguments[i]);
                    }
                    return Expression.Call(method, callArguments);
                }                
            }
            throw new DlrBasicException(Properties.Resources.FunctionNotFoundException);
        }

        protected internal override Expression GenerateAssign(DlrBasicGenerator generator, Expression right)
        {
            if (IsFunction()) throw new SyntaxErrorException(Properties.Resources.FunctionOnLeftException);

            Type itemType = GetVariableType(generator);
            System.Reflection.MethodInfo method = TypeConversionUtils.FindExactMethod(
                typeof(Runtime.DlrBasicMemory), "Set", itemType, true);

            Expression index = Expression.Null(typeof(int[]));
            if (ArgumentsCount > 0)
            {
                Expression[] bounds = new Expression[arguments.Length];
                for (int i = 0; i < bounds.Length; i++)
                {
                    bounds[i] = Expression.ConvertHelper(
                        TypeConversionUtils.BuildConversionTo(typeof(short),
                        arguments[i].Generate(generator)), typeof(int));
                }
                index = Expression.NewArrayInit(typeof(int), bounds);
            }

            return Expression.Call(new StateMemberGenerator(generator.CurrentStateParameter).GetMemoryVariable(),
                method,
                Expression.Constant(SymbolTable.StringToCaseInsensitiveId(Name)),
                index, 
                TypeConversionUtils.BuildConversionTo(itemType, right));
        }

        public Expression GenerateArrayDefinition(DlrBasicGenerator generator)
        {
            if (ArgumentsCount > 0)
            {
                Expression[] bounds = new Expression[arguments.Length];
                for (int i = 0; i < bounds.Length; i++)
                {
                    bounds[i] = Expression.ConvertHelper(
                        TypeConversionUtils.BuildConversionTo(typeof(short),
                        arguments[i].Generate(generator)), typeof(int));
                }
                Expression boundsArray = Expression.NewArrayInit(typeof(int), bounds);

                Type itemType = GetVariableType(generator);
                return Expression.Call(generator.GetCurrentStateParameterGenerator().GetMemoryVariable(),
                    typeof(Runtime.DlrBasicMemory).GetMethod("InitArray"),
                    Expression.Constant(SymbolTable.StringToCaseInsensitiveId(Name)),
                    Expression.Constant(itemType), boundsArray);
            }

            throw new NotSupportedException();
        }

        public void GenerateFunctionParameters(DlrBasicGenerator generator, out string[] parameterNames, out Type[] parameterTypes)
        {
            parameterNames = new string[ArgumentsCount];
            parameterTypes = new Type[ArgumentsCount];
            for (int i = 0; i < parameterNames.Length; i++)
            {
                VariableOrCall variable = (VariableOrCall)arguments[i].GetSingleLeaf(generator);
                parameterNames[i] = variable.Name;
                parameterTypes[i] = variable.GetVariableType(generator);
            }
        }

        protected internal override DlrBasicExpression GetSingleLeaf(DlrBasicGenerator generator)
        {
            return this;
        }
    }
}
